// CheckBox.cpp : implementation file
//
#ifndef OBLIVION

#include "StdAfx.h"
#include "CheckBox.h"


#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//
// CCheckBox construction/destruction

//-----------------------------------------------------------------------------
// Name:		CCheckBox
// Description:	Constructor
//
// Entry:		
//				int rcID		- resource ID with state bitmaps
//
//				int dimension	== width == height of a state bitmap
//-----------------------------------------------------------------------------
CCheckBox::CCheckBox (int rcID,int dimension) : 
  m_rcID( rcID ), 
  BOX_SIZE( dimension )
{	
	_ASSERTE (BOX_SIZE > 1);
	_ASSERTE (BOX_SIZE < 30);

	// load state bitmaps
	//
	m_bSrc = LoadBitmap (AfxGetResourceHandle(),MAKEINTRESOURCE(m_rcID));
	_ASSERTE (m_bSrc != 0);	// * bitmap resource rcID valid ? * //
	
	m_bLeftText = false;
	m_bChecked  = BST_UNCHECKED;

	inRect    = false;
	spaceDown = false;
	mouseDown = false;
	
	m_brush = NULL;

	hasFocus = false;

	prepare_bitmaps = true;

	for (int i=0; i < BOX_MAX; ++i)
		m_bitmaps [i] = 0;
}
  
//-----------------------------------------------------------------------------
// Name:		~CCheckBox
// Description:	Destructor
//-----------------------------------------------------------------------------
CCheckBox::~CCheckBox()
{
	for (int i=0; i < BOX_MAX; ++i) {   
		if (m_bitmaps [i])
			DeleteObject (m_bitmaps [i]);
	}

	DeleteObject (m_font);
}



//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//
// CCheckBox overrides

//-----------------------------------------------------------------------------
// Name:		PreSubclassWindow
// Description:	Makes some initialisations before the actuall 
//				subclassing occurs
//-----------------------------------------------------------------------------
void CCheckBox::PreSubclassWindow() 
{
	// text on left side?
	//
	unsigned style    = GetStyle   ();
	unsigned ex_style = GetExStyle ();

	if ((style	  & BS_LEFTTEXT)    || 
		(style	  & BS_RIGHTBUTTON) || 
		(ex_style & WS_EX_RIGHT) )
		m_bLeftText = true;
	

	GetWindowText (m_text, 64);										// get text
	m_font = (HFONT) GetParent()->SendMessage (WM_GETFONT, 0, 0);	// get font
	
	// prevent any drawing by the control itself
	//
	ModifyStyle (0,BS_OWNERDRAW,0);
		
	
	CButton::PreSubclassWindow();
}


//-----------------------------------------------------------------------------
// Name:		DrawItem
// Description:	We have to override this, because CButton::DrawItem
//				only executes an ASSERT
//-----------------------------------------------------------------------------
void CCheckBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	return;
}

/*
//-----------------------------------------------------------------------------
// Name:		PreTranslateMessage
// Description:	WM_LBUTTONDBLCLK => WM_LBUTTONDOWN
//					- to ensure proper state repainting of the control
//					- to prevent BN_DBLCLK notifications to the parent 
//					  (BS_OWNERDRAW buttons send them, but CBs don't)
//-----------------------------------------------------------------------------
BOOL CCheckBox::PreTranslateMessage(MSG* pMsg) 
{
	if (pMsg->message == WM_LBUTTONDBLCLK)
		pMsg->message = WM_LBUTTONDOWN;
	
	return CButton::PreTranslateMessage(pMsg);
}
*/


//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//
// CCheckBox message handlers

BEGIN_MESSAGE_MAP(CCheckBox, CButton)
	//{{AFX_MSG_MAP(CCheckBox)
	ON_WM_PAINT()
	ON_WM_ERASEBKGND()
	ON_WM_ENABLE()
	ON_WM_KEYDOWN()
	ON_WM_KEYUP()
	ON_WM_LBUTTONDBLCLK()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_SETFOCUS()
	ON_WM_KILLFOCUS()	
	//}}AFX_MSG_MAP	
	ON_MESSAGE (BM_GETCHECK, OnGetCheck)
	ON_MESSAGE (BM_SETCHECK, OnSetCheck)
END_MESSAGE_MAP()


//-----------------------------------------------------------------------------
// Name:		OnPaint
// Description:	Blits the prepared bitmaps to the screen
//-----------------------------------------------------------------------------
void CCheckBox::OnPaint() 
{
	DWORD clr1 = ::GetSysColor (COLOR_3DFACE);
	if (m_clr != clr1) {			// sysColor changed ??				
		for (int i=0; i < BOX_MAX; ++i) {	// => prepare bitmaps again
			if (m_bitmaps [i])
				DeleteObject (m_bitmaps [0]);
		}
		m_clr = clr1;

		if (m_brush) DeleteObject (m_brush);
		m_brush = ::CreateSolidBrush (m_clr);

		prepare_bitmaps = true;
	}

	CPaintDC dc (this);

	if (prepare_bitmaps) {			// called first time or sysColor changed?
		PrepareBitmaps (dc.m_hDC);	// => prepare bitmaps
		prepare_bitmaps = false;
	}
	BlitToScreen (dc.m_hDC);
}


//-----------------------------------------------------------------------------
// Name:		OnEraseBkgnd
// Description:	Prevent any background erasing
//-----------------------------------------------------------------------------
BOOL CCheckBox::OnEraseBkgnd(CDC* pDC) 
{		
	return 1;
}


//-----------------------------------------------------------------------------
// Name:		OnEnable
// Description:	Enable or disable CB
//-----------------------------------------------------------------------------
void CCheckBox::OnEnable(BOOL bEnable) 
{
	CButton::OnEnable(bEnable);

	mouseDown = false;
	spaceDown = false;

	RECT rc;
	GetClientRect(&rc);
	InvalidateRect(&rc, FALSE);
}


//-----------------------------------------------------------------------------
// Name:		OnKeyDown
//-----------------------------------------------------------------------------
void CCheckBox::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (nChar == VK_SPACE && !spaceDown) {
		spaceDown = true;
		

		RECT rc;
		GetClientRect(&rc);
		InvalidateRect(&rc, FALSE);
	}
	else if (nChar != VK_SPACE && spaceDown) {
        spaceDown = false;
		

		RECT rc;
		GetClientRect(&rc);
		InvalidateRect(&rc, FALSE);
	}
	
	CButton::OnKeyDown(nChar, nRepCnt, nFlags);
}


//-----------------------------------------------------------------------------
// Name:		OnKeyUp
//-----------------------------------------------------------------------------
void CCheckBox::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if (nChar == VK_SPACE && spaceDown) {
		spaceDown = false;

		RECT rc;
		GetClientRect(&rc);
		InvalidateRect(&rc, FALSE);
	}
	
	CButton::OnKeyUp(nChar, nRepCnt, nFlags);
}


//-----------------------------------------------------------------------------
// Name:		OnLButtonDown
//-----------------------------------------------------------------------------
void CCheckBox::OnLButtonDown(UINT nFlags, CPoint point) 
{
	mouseDown = true;
	inRect = true;

	CButton::OnLButtonDown(nFlags, point);  // also calls "SetCapture" & "SetFocus"

	RECT rc;
	GetClientRect(&rc);
	InvalidateRect(&rc, FALSE);
}


//-----------------------------------------------------------------------------
// Name:		OnMouseMove
//-----------------------------------------------------------------------------
void CCheckBox::OnMouseMove(UINT nFlags, CPoint pt) 
{
	RECT rc;
	GetClientRect (&rc);

	if (mouseDown)
	{
		if (!PtInRect (&rc, pt)) {
			inRect = false;
			InvalidateRect(&rc, FALSE);
		}
		else if (!inRect) {
			inRect = true;
			InvalidateRect(&rc, FALSE);
		}
	}
	
	CButton::OnMouseMove(nFlags, pt);
}


//-----------------------------------------------------------------------------
// Name:		OnLButtonUp
//-----------------------------------------------------------------------------
void CCheckBox::OnLButtonUp(UINT nFlags, CPoint pt) 
{
	mouseDown = false;

	RECT rc;
	GetClientRect (&rc);
					
	if (PtInRect (&rc, pt))
	{
		switch(m_bChecked)
		{
			case BST_CHECKED:
				SetCheck(BST_INDETERMINATE);
				break;

			case BST_INDETERMINATE:
				SetCheck(BST_UNCHECKED);
				break;

			case BST_UNCHECKED:
			default:
				SetCheck(BST_CHECKED);
				break;
		}
	}
										
	CButton::OnLButtonUp(nFlags, pt);	// also calls "ReleaseCapture"
}


//-----------------------------------------------------------------------------
// Name:		OnSetFocus
//-----------------------------------------------------------------------------
void CCheckBox::OnSetFocus(CWnd* pOldWnd) 
{	
/*	hasFocus = drawFocus;
	DrawFocus ();

*/	CButton::OnSetFocus(pOldWnd);
}


//-----------------------------------------------------------------------------
// Name:		OnKillFocus
//-----------------------------------------------------------------------------
void CCheckBox::OnKillFocus(CWnd* pNewWnd) 
{
/*	hasFocus = false;
	DrawFocus ();

*/	CButton::OnKillFocus(pNewWnd);

	if (spaceDown) {			// mouse pressed in another window, 
		spaceDown = false;		// while spaceDown

		RECT rc;
		GetClientRect(&rc);
		InvalidateRect(&rc, FALSE);
	}
}


/*
//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//
// CCheckBox drawing methods

//-----------------------------------------------------------------------------
// Name:		DrawFocus
// Description:	Draws a focus rectangle
//
// Entry:		
//				bool forceDraw - this parameter can be one of the following:
//					 false : draw focus rect only if "hasFocus" has changed
//					 true  : draw focus rect regardles of previous value 
//							 of "hasFocus" 
//
//					hasFocus - has the following meaning:
//					true  : check box has the keyboard focus at the moment
//					false : check box is not focused at the moment
//-----------------------------------------------------------------------------
void CCheckBox::DrawFocus (bool forceDraw)
{	
	if (!drawFocus)   // draw focus rectangle?
		return;

	static bool prev = false;
	if (prev != hasFocus || forceDraw) {
		
		prev = hasFocus;

		CDC* pdc = GetDC ();		
		DrawFocusRect (pdc->m_hDC,&rcFocus);
		ReleaseDC (pdc);		
	}
}*/


//-----------------------------------------------------------------------------
// Name:		OnGetCheck
// Description:	recieved BM_GETCHECK - either directly or through:
//						- CWnd::IsDlgButtonChecked or
//						- CButton::GetCheck wrapper
//-----------------------------------------------------------------------------
LRESULT CCheckBox::OnGetCheck(WPARAM wParam, LPARAM lParam)
{
	return m_bChecked;
}


//-----------------------------------------------------------------------------
// Name:		OnSetCheck
// Description:	recieved BM_SETCHECK - either directly or through 
//				CButton::SetCheck wrapper
//-----------------------------------------------------------------------------
LRESULT CCheckBox::OnSetCheck(WPARAM wParam, LPARAM lParam)
{
	m_bChecked = wParam;

	RECT rc;
	GetClientRect(&rc);
	InvalidateRect(&rc, FALSE);

	return 0;
}



//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
//
// CCheckBox drawing methods

//-----------------------------------------------------------------------------
// Name:        GetCheckID
// Description: Determines the position of the appropriate checkmark
//
//-----------------------------------------------------------------------------
UINT CCheckBox::GetCheckID()
{
	if ( !IsWindowEnabled() )
		return BOX_DISABLED_OFF + m_bChecked;
	else if ( (mouseDown && inRect) || spaceDown )
		return BOX_LDOWN_OFF + m_bChecked;
	else
		return m_bChecked;
}


//-----------------------------------------------------------------------------
// Name:		BlitToScreen
// Description:	Blits (=draw, copy,..) "m_bitmaps [nState]" to the screen
//
// Entry:		
//				HDC hdc - DC to blit to
//
//-----------------------------------------------------------------------------
void CCheckBox::BlitToScreen (HDC hdc)
{
	int nState = GetCheckID();
	HDC     destDC = ::CreateCompatibleDC (hdc);
	HBITMAP oDest;
	oDest = (HBITMAP) ::SelectObject (destDC,m_bitmaps [nState]);

	RECT rect;
	GetClientRect(&rect);

	int width  = rect.right  - rect.left;
	int height = rect.bottom - rect.top;

	if(height >= mHeight) {
		rect.top    += (height - mHeight)/2;
		rect.bottom -= (height - mHeight)/2;
		height = rect.bottom - rect.top;
	}

	// Blit pre-prepared bitmap to screen
	//	
	BitBlt(hdc, rect.left, rect.top, width, height, destDC, 0, 0, SRCCOPY);
	
	// Write the text
	//
	DrawText_(hdc, rect, nState);
	

	// Clean up
	//	
	SelectObject(destDC, oDest);
	
	DeleteDC(destDC);	
}


//-----------------------------------------------------------------------------
// Name:		GetMinHeight
// Description:	Calculates minimal height of CB based on m_font
//
// Entry:		
//				HDC hdc - DC into which m_font is selected
//			
//-----------------------------------------------------------------------------
int CCheckBox::GetMinHeight (HDC hdc)
{
	HFONT oFont = (HFONT) SelectObject (hdc,m_font);
	SIZE sz; GetTextExtentPoint32 (hdc,"Dummy",5, &sz);
	SelectObject (hdc,oFont);

	return sz.cy+1;
}


//-----------------------------------------------------------------------------
// Name:		PrepareBitmaps & PrepareState
// Description:	This methods prepare "m_bitmaps[???]" for all posible states
//
// Entry:		
//				HDC     hdc - DC used to create memory DCs for bitmap preparation
//
//				HBITMAP &bDest - this parameter is one of the following:
//						m_bitmaps [0] : BOX_ON  bitmap
//						m_bitmaps [1] : BOX_OFF bitmap
//						...
//						m_bitmaps [8] : BOX_DISABLED_IND bitmap
//
//				int nState - specifies the state of the CB and can be:
//					BOX_ON
//					BOX_OFF
//					BOX_IND
//					BOX_LDOWN_ON
//					BOX_LDOWN_OFF
//					BOX_LDOWN_IND
//					BOX_DISABLED_ON
//					BOX_DISABLED_OFF
//					BOX_DISABLED_IND
//-----------------------------------------------------------------------------
void CCheckBox::PrepareBitmaps (HDC hdc)
{
	mHeight = GetMinHeight (hdc);
	if (mHeight < BOX_SIZE)
		mHeight = BOX_SIZE;
	
	for ( int i = 0; i < BOX_MAX; ++i )
		PrepareState(hdc, m_bitmaps[i], i);
}

void CCheckBox::PrepareState (HDC hdc, HBITMAP & bDest, int nState)
{
	RECT rect;
	GetClientRect(&rect);

	int width  = rect.right  - rect.left;
	int height = rect.bottom - rect.top;

	if(height >= mHeight) {
		rect.top    += (height - mHeight)/2;
		rect.bottom -= (height - mHeight)/2;
		height = rect.bottom - rect.top;
	}
	int o = (mHeight - BOX_SIZE) / 2;

	//
	// Create memory DCs and bitmaps to prepare the image
	//
	HDC srcDC  = ::CreateCompatibleDC (hdc);
	HDC maskDC = ::CreateCompatibleDC (hdc);
	HDC destDC = ::CreateCompatibleDC (hdc);


	HBITMAP bMask = ::CreateBitmap (BOX_SIZE,BOX_SIZE,1,1,NULL);
			bDest = ::CreateCompatibleBitmap (hdc,width,mHeight);		  

	HBITMAP oSrc  = (HBITMAP) ::SelectObject (srcDC, m_bSrc);	
	HBITMAP oMask = (HBITMAP) ::SelectObject (maskDC,bMask);
	HBITMAP oDest = (HBITMAP) ::SelectObject (destDC,bDest);	
	

	// Create mask - mask color: RGB(255,0,255)
	//
	COLORREF color = SetBkColor(srcDC, RGB(255,0,255));	


	RECT rc;				// rect = drawing rectangle inside client area of CB
	rc.left	  = rc.top = 0;	// rc   = drawing rectangle inside destDC 
	rc.right  = width;		// right of rc = (width) of rect
	rc.bottom = height;     // bottom of rc = (height) of rect
	

	// copy the display surface where the CheckBox will be to destDC
	//
#if TRANSPARENT_CB
	BitBlt (destDC,0,0,width,height,hdc, rect.left, rect.top, SRCCOPY);
#else
	FillRect (destDC, &rc, m_brush);
#endif

	int l = 0;
	if (m_bLeftText) l = width - BOX_SIZE;		

	int x = BOX_SIZE*nState;  
	BitBlt(maskDC, 0, 0, BOX_SIZE, BOX_SIZE, srcDC, x, 0, SRCCOPY);
	BitBlt(destDC, l, o, BOX_SIZE, BOX_SIZE, srcDC, x, 0, SRCINVERT); 
	BitBlt(destDC, l, o, BOX_SIZE, BOX_SIZE,maskDC, 0, 0, SRCAND);
	BitBlt(destDC, l, o, BOX_SIZE, BOX_SIZE, srcDC, x, 0, SRCINVERT);

	// Clean up
	//
	SelectObject(srcDC,  oSrc);
	SelectObject(maskDC, oMask);
	SelectObject(destDC, oDest);
	
	DeleteDC(srcDC);
	DeleteDC(maskDC);
	DeleteDC(destDC);

	DeleteObject(bMask);
}


//-----------------------------------------------------------------------------
// Name:		DrawText_
// Description:	Draws text for the CB to destDC
//				(into which one of the "m_bitmaps [???]" is selected)
//
// Entry:		
//				HDC destDC - DC to which text is drawed
//							 ("m_bitmaps [???]" is selected into it)
//
//				RECT rc    - part of client area (of the CB) to which
//							 text is output
//			
//-----------------------------------------------------------------------------
void CCheckBox::DrawText_ (HDC destDC, RECT rc, int nState)
{	
	int oState = SaveDC (destDC);

	SetBkMode (destDC, TRANSPARENT);
	SelectObject (destDC,*(GetParent()->GetFont()));
	

	rc.left += 2;
	if (m_bLeftText)
		rc.right -= BOX_SIZE+6;
	else
		rc.left  += BOX_SIZE+5;
	
	UINT flags = DST_TEXT;

	if ( !IsWindowEnabled() )
		flags |= DSS_DISABLED;
	
	CString buf;
	GetWindowText(buf);
	DrawState(destDC, NULL, NULL, (LPARAM)buf.GetBuffer(), 0, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top, flags);

	RestoreDC (destDC, oState);
}

#endif
